package com.qyer.commons.utils;

import com.qyer.commons.exception.TooManyRoundsOfRetryingException;

import java.util.concurrent.TimeUnit;

/**
 * User: Z J Wu Date: 2016/06/30 Time: 14:39 Package: com.qyer.pushengine.ios.exception
 */
public class ReentrantExceptionOperator<R> {

  private int currentOperationCount;

  private int maxOperationCount;

  private RepeatableOperation<R> operation;

  private TimeUnit timeUnit;
  private long retryInterval;

  public ReentrantExceptionOperator(int maxOperationCount, RepeatableOperation operation) {
    this.maxOperationCount = maxOperationCount;
    this.operation = operation;
  }

  public ReentrantExceptionOperator(int maxOperationCount, RepeatableOperation<R> operation,
                                    TimeUnit timeUnit, long retryInterval) {
    this.maxOperationCount = maxOperationCount;
    this.operation = operation;
    this.timeUnit = timeUnit;
    this.retryInterval = retryInterval;
  }

  public static <R> ReentrantExceptionOperator<R> buildOperationWithoutSleep(int maxOperationCount,
                                                                             RepeatableOperation<R> operation) {
    return new ReentrantExceptionOperator(maxOperationCount, operation);
  }

  public static <R> ReentrantExceptionOperator buildOperationWithDefaultSleep(int maxOperationCount,
                                                                              RepeatableOperation<R> operation) {
    return new ReentrantExceptionOperator(maxOperationCount, operation, TimeUnit.MILLISECONDS, 500);
  }

  public static <R> ReentrantExceptionOperator buildOperationWithSleep(int maxOperationCount,
                                                                       RepeatableOperation<R>
                                                                         operation,
                                                                       TimeUnit timeUnit,
                                                                       long retryInterval) {
    return new ReentrantExceptionOperator(maxOperationCount, operation, timeUnit, retryInterval);
  }

  public RepeatableOperationResult<R> tryOperate() throws TooManyRoundsOfRetryingException,
    InterruptedException {
    Exception lastException = null;
    R result = null;
    while (currentOperationCount < maxOperationCount) {
      ++currentOperationCount;
      try {
        result = operation.doOperation();
        break;
      } catch (Exception e) {
        if (timeUnit != null) {
          timeUnit.sleep(retryInterval);
        }
        if (currentOperationCount == maxOperationCount - 1) {
          lastException = e;
        }
      }
    }
    if (lastException != null) {
      throw new TooManyRoundsOfRetryingException(
        "Retry " + currentOperationCount + " times but still failed.", lastException);
    }
    return new RepeatableOperationResult<>(currentOperationCount, result);
  }

  public void clear() {
    this.currentOperationCount = 0;
  }

}
